/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package se.kth.lwr.jeat.core;

import java.util.*;
import javax.swing.Action;
import javax.swing.tree.TreeNode;
import se.kth.lwr.jeat.utils.UmberellaActivity;

/**
 * The <code>Activity</code> is a base class for all kinds of events in an
 * ESA model that will affect the environment.
 * <p/>
 * When constructing complex activities you should consider breaking them down
 * into sub activities and join them in parent child relation ships.
 * <p/>
 * Besides the hierarchical nature of Activities, they can be
 * cross-clustered by use of ActivityCategories ({
 * @link ActivityCategory}).
 *
 * <p/>
 * The core work of the Activity is taking place in the abstract method
 * {@link contribute(Inventory inventory)}. In this method the Activity is 
 * supposed to reflect its environmental impact by adding to the passed 
 * {@link Inventory} the emissions and resource consumption it cause. The
 * Inventory argument passed to this method holds the sum of the 
 * impact from all child activities. Thus, if the Activity is just an aggregating 
 * construct (as the {@link UmberellaActivity}), the Inventory should be just 
 * passed forward. Otherwise manipulations should be made (true for lief 
 * activities and hybride).
 * <p/>
 *<p>
 * All data used for computation of the impact from an activity that needs to be
 * traceable have to be wrapped into a DataSource Object.{@see DataSource} 
 * for usage. There are several strengths of wrapping data into
 * DataSources. Except traceability, it is much more safe from a human error
 * point of view. This is due to the explicit use of units enforced by the
 * DataSource objects provided by the API.
 * </p>
 *
 * <p>
 * Jag har valt att använda TreeNode interfacet för att hantera child-parent
 * assosiationen. Som en bieffekt får jag då också stöd för hantering av
 * aktiviteter i trädvyer i SWING. Nackdelen är möjligen en något tillkrånglad
 * implementering. Exempelvis, ser jag inget behov av att ordna noderna i serie.
 * </p>
 *
 * @author David
 */
public abstract class Activity extends Described implements TreeNode, Inventoriable {

    //Child activities
    private List<Activity> childActivities = new ArrayList<>();
    //parent activiry
    private Activity parentActivity = null;
    //ActivityCategories, that this Activity belongs to
    private Set<ActivityCategory> categories = new HashSet<>();
    //DataSources that this Activity is using
    private Set<DataSource> sources = new TreeSet<>();

    /**
     * Creates an activity
     * 
     * @param name name of the activity
     * @param description descrption of activity
     */
    public Activity(String name, String description) {
        super(name, description);

    }

    /**
     * Removes the child Activity from this Activity if it is a present child
     *
     * @param child the child activity to remove
     */
    public void removeChild(Activity child) {
        //remove child from parent
        if (childActivities.remove(child)) {
            //remove parent from child
            child.setParent(null);
        } else {
            //leave it untouched
        }
    }

    /**
     * Add an activity as a child to this activity. Activities is bound to one
     * and only one parent (if any) and this method will remove the child
     * activity from the former parent (if it had one). An
     * IllegalArgumentException is thrown if the passed object is allready a
     * child of this activity.
     *
     * @param child The child activity to add
     */
    public void addChild(Activity child) {
        //check and prohibit double addition
        if (childActivities.contains(child)) {
            throw new IllegalArgumentException("Child allready exists");
        } else {
            childActivities.add(child);
        }
        //remove child from old parent
        if (child.getParent() != null) {
            child.getParent().removeChild(child);
        }

        //move child to new parent
        child.setParent(this);


    }

    /**
     * Sets the parent of this Activity. The parent should be set to null if the
     * activity has no parent (a root activity).
     *
     * @param parent
     */
    protected void setParent(Activity parent) {
        parentActivity = parent;
    }

    /**
     * The Inventory passed as argument to this method captures the emissions
     * and consumptions from any child activities. Manipulate the inventory to
     * reflect the contribution from this particular activity. If this Activity
     * is just an aggregation of child activities, just pass the inventory
     * forward.
     * 
     * @param inventory Contribution of child activities
     * @return the Inventory for this Activity including its children
     */
    public abstract Inventory contribute(Inventory inventory);

    /**
     * This function should return an Inventory for this Activity including all
     * its children. In the general case the {@link Contribute} method is
     * intended for definition of emissions and resource consumptions that arise
     * from the activity.
     *
     * @return an Inventory that reflects emissions and resource consumption
     * that arise from this activity including all its children.
     *
     * Funderar på om denna funktion skulle vara deklarerad final vilket skulle
     * förhindra missförstånd. Dock osäker på om det kan finnas situationer då
     * användaren skulle kunna ha nytta av att definiera om den.
     */
    @Override
    public Inventory getInventory() {

        /**
         * Summera Inventories från alla barn och anropa contribute på den
         * summerade Inventorien
         */
        // create a new empty Inventory
        Inventory inventory = Inventory.emptyInventory();
        if (!isLeaf()) {
            for (Activity activity : childActivities) {

                inventory = inventory.merge(activity.getInventory());
            }
        }
        return contribute(inventory);

    }

    /**
     * This method returns an Inventory for this Activity including all its
     * descendants but the returned Inventory only includes emissions that 
     * arises from activities that belongs to the given ActivityCategory. If the
     * passed ActivityCategory is null no filtering will be performed.
     *
     * @param filter the ActivityCategory to select
     * @return An Inventory of the sum of of all activities that belongs to the
     * given ActivityCategory and is a descendant of this activity or this
     * activity itself.
     */
    public Inventory getInventory(ActivityCategory filter) {
        /*
         * first check if there is any filter
         */

        if (filter == null) {
            return getInventory();
        }

        /**
         * Summera Inventories från alla barn som är ansluten till kategorin och
         * anropa contribute på den summerade Inventorien om DENNA aktivitet är
         * ansluten till kategorin
         */
        // create a new empty Inventory
        Inventory inventory = Inventory.emptyInventory();
        if (!isLeaf()) {
            for (Activity activity : childActivities) {
                //testa tillhörighet endast på lövaktivteter, annars klipper
                //vi så fort en aktivitet i ledet inte tillhör kategorin
                if (activity.isLeaf()) {
                    if (activity.isJoinedTo(filter)) {
                        inventory = inventory.merge(activity.getInventory());
                    }
                } else {
                    //forsätt rekursivt
                    inventory = inventory.merge(activity.getInventory(filter));
                }
            }
        }
        //missa inte denna aktivitets eventuella bidrag
        if (isJoinedTo(filter)) {
            inventory = this.contribute(inventory);
        }
        return inventory;

    }
    
    /**
     * 
     * @return All child activities of this activity
     */

    @Override
    public Enumeration<Activity> children() {
        return Collections.enumeration(childActivities);
    }
    
    /**
     * 
     * @return whether this activity allows children or not
     */

    @Override
    public boolean getAllowsChildren() {
        return true;
    }
    
    /**
     * Returns child at certain index. Throws an indexOutOfBoundException if
     * <code>index>=child count</code>
     * 
     * @param childIndex child index
     * @return a child activity
     */

    @Override
    public Activity getChildAt(int childIndex) {
        return childActivities.get(childIndex);
    }
    
    /**
     * 
     * @return number of children 
     */

    @Override
    public int getChildCount() {
        return childActivities.size();
    }
    
    /**
     * 
     * @param child a children to this activity
     * @return the index of the passed child or -1 if the child do not exist
     */

    @Override
    public int getIndex(TreeNode child) {
        return childActivities.indexOf(child);
    }
    
    /**
     * 
     * @return the parent of this activities
     */

    @Override
    public Activity getParent() {
        return parentActivity;
    }
    
    /**
     * 
     * @return true if this activity is a leaf activity, false otherwise
     */

    @Override
    public boolean isLeaf() {
        if (childActivities.isEmpty()) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Lets this Activity join an ActivityCategory
     *
     * @param category the ActivityAcategory to join
     * @return true if the Activity joined the category. False if the Activity
     * already belonged to the category.
     */
    public boolean joinCategory(ActivityCategory category) {
        categories.add(category);
        return category.addActitivity(this);

    }

    /**
     * Let this Activity AND ALL ITS DESCENDANTS join an ActivityCategory
     *
     * @param category the ActivityCategory to join
     *
     */
    public void joinCategoryDeep(ActivityCategory category) {
        final ActivityCategory cat=category;
        //create an ActivityOperator to use with an ActivityTraverser
        ActivityOperator operator=new ActivityOperator() {
            @Override
            public void operate(Activity a) {
                a.joinCategory(cat);
            }
        };
        //Create a traverser and let it operate on this activity
        new ActivityTraverser(operator).operateTraversal(this);
    }


    /**
     * Lets this Activity disJoin an ActivityCategory
     *
     * @param category The ActivityAcategory to disjoin
     * @return true if the Activity was disjoined. False if the Activity did not
     * belong to the category
     */
    public boolean disJoinCategory(ActivityCategory category) {
        categories.remove(category);
        return category.removeActivity(this);
    }

    /**
     * Get a Set of ActivityCategories to which this activity belongs
     *
     * @return An unmodifiable Set of the Categories that this Activity belongs
     * to
     */
    public Set<ActivityCategory> joinedCategories() {
        return Collections.unmodifiableSet(categories);
    }

    /**
     * Get a Set with all DataSources that this activity is using
     *
     * @return an unmodifiable Set of DataSources
     */
    public Set<DataSource> getSources() {
        return Collections.unmodifiableSet(sources);
    }

    /**
     * Adds this source to a list of sources used by this activity
     *
     * @param source The DataSource that have been used by computations in this
     * activity
     * @return True if the source was added, False if the source was null or if
     * the source has already been used by this activity
     */
    protected boolean addSource(DataSource source) {
        return sources.add(source);
    }
    
    /**
     * 
     * @return a string representation of this activity
     */

    @Override
    public String toString() {
        return getName() + ", " + getDescription();
    }

    /**
     * Indicates if this activity belongs to a specific {@link ActivityCategory}
     *
     * @param category activity category to check
     * @return true if this activity belongs to the ActivityCategory false
     * otherwise
     */
    private boolean isJoinedTo(ActivityCategory category) {
        return joinedCategories().contains(category);
    }

    /**
     * Class for performing traversal operations on Activities
     */

    private class ActivityTraverser {

        ActivityOperator operator;

        public ActivityTraverser(ActivityOperator op) {
            this.operator = op;
        }
        /**Operates on all descendants and the parent activity itself
         * 
         * @param activity 
         */
        public void operateTraversal(Activity activity){
            operator.operate(activity);
            operateOnDescendants(activity);
        }
        
        /**operates on the descendants only
         * 
         * @param parant 
         */

        public void operateOnDescendants(Activity parant) {
            for (Enumeration<Activity> e = parant.children(); e.hasMoreElements();) {
                Activity ac=e.nextElement();
                operator.operate(ac);
                operateOnDescendants(ac);
            }

        }
    }
    
    /**
     * simple interface for performing operations on Activities
     */
    private interface ActivityOperator {

        public void operate(Activity a);
    }
}
